package cn.iocoder.yudao.framework.pay.core.client.impl.pax;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.enums.CurrencyEnum;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.enums.GoodsCategoryEnum;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.enums.GoodsTypeEnum;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.enums.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.enums.PayMethodEnum;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.enums.TerminalTypeEnum;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxCreatePrePayOrderRequest;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxCreatePrePayOrderResponse;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxInvokeRefundOrderRequest;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxInvokeRefundOrderResponse;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxNoticePrepayOrderStatusResponse;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxNoticeRefundOrderStatusResponse;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxResult;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxQueryPrePayOrderRequest;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxQueryPrePayOrderResponse;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxQueryRefundOrderRequest;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.model.PaxQueryRefundOrderResponse;
import cn.iocoder.yudao.framework.pay.core.client.impl.pax.util.PaxOpenSdk;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.alibaba.fastjson.JSON;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

/**
 * Pax的支付客户端的抽象类
 *
 * @author cai
 * @date 2024/02/26 16:44
 */
@Slf4j
public abstract class AbstractPaxClient extends AbstractPayClient<PaxClientConfig> {

    @Getter // 仅用于单测场景
    protected DefaultPaxClient client;

    public AbstractPaxClient(Long channelId, String channelCode, PaxClientConfig config) {
        super(channelId, channelCode, config);
    }

    @Override
    protected void doInit() {
        this.client = new DefaultPaxClient(this.config);
    }

    @Override
    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable {
        // 1.1 构建 CreatePrePayOrderRequest 请求
        PaxCreatePrePayOrderRequest request = new PaxCreatePrePayOrderRequest();
        buildCreatePrePayOrderRequest(reqDTO, request);

        // 2.1 执行请求
        PaxResult<PaxCreatePrePayOrderResponse> result = client.execute(request, PaxCreatePrePayOrderResponse.class);
        // 2.2 处理结果
        if (!result.checkSuccess()) {
            return buildClosedPayOrderRespDTO(reqDTO, result);
        }
        return buildSuccessPayOrderRespDTO(reqDTO, result);
    }

    @Override
    protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable {
        // 1.1 构建 QueryPrePayOrderRequest 请求
        PaxQueryPrePayOrderRequest request = new PaxQueryPrePayOrderRequest();
        request.setMerchantTradeNo(outTradeNo);
        // 2.1 执行请求
        PaxResult<PaxQueryPrePayOrderResponse> result = client.execute(request, PaxQueryPrePayOrderResponse.class);
        if (!result.checkSuccess()) { // 不成功，例如说订单不存在
            return PayOrderRespDTO.closedOf(result.getCode(), result.getMsg(),
                outTradeNo, result);
        }
        // 2.2 解析订单的状态
        PaxQueryPrePayOrderResponse data = result.getData();
        Integer status = parseStatus(data.getOrderStatus());
        Assert.notNull(status, () -> {
            throw new IllegalArgumentException(StrUtil.format("body({}) 的 order_status 不正确", JSON.toJSONString(result)));
        });
        // channelUserId暂时没有值
        Long successTime = data.getPaySuccessTime();
        // TODO: 临时解决空指针
        if (Objects.isNull(successTime)) {
            successTime = System.currentTimeMillis();
        }
        return PayOrderRespDTO.of(status, data.getPaxPrepayId(), "", LocalDateTimeUtil.of(successTime),
            outTradeNo, result);
    }

    @Override
    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws Throwable {
        throw new UnsupportedOperationException("Pax不实现此方法");
    }

    @Override
    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        PaxOpenSdk.PaxHeader paxHeader = PaxOpenSdk.createPaxBaseHeader(headers);
        log.info("[doParseOrderNotify][paxHeader: {}]", paxHeader);
        boolean verifySign = PaxOpenSdk.verifySign(paxHeader, body, this.config.getPublicKey());
        if (!verifySign) {
            throw new IllegalArgumentException("Pax支付回调验签失败");
        }
        // 1.1 解析 body
        PaxNoticePrepayOrderStatusResponse response = JSON.parseObject(body, PaxNoticePrepayOrderStatusResponse.class);
        // 2. 解析订单的状态
        Integer status = parseStatus(response.getOrderStatus());
        Assert.notNull(status, (Supplier<Throwable>) () -> {
            throw new IllegalArgumentException(StrUtil.format("body({}) 的 orderStatus 不正确", body));
        });
        Long successTime = response.getPaySuccessTime();
        // TODO: 临时解决空指针
        if (Objects.isNull(successTime)) {
            successTime = System.currentTimeMillis();
        }
        return PayOrderRespDTO.of(status, response.getPaxPrepayId(), "", LocalDateTimeUtil.of(successTime),
            response.getMerchantTradeNo(), body);
    }

    @Override
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        // 1.1 构建 InvokeRefundOrderRequest 请求
        PaxInvokeRefundOrderRequest request = PaxInvokeRefundOrderRequest.builder()
            .paxPrepayId(reqDTO.getChannelTradeNo())
            .merchantRefundId(reqDTO.getOutRefundNo())
            .refundAmount(formatAmount(reqDTO.getRefundPrice()))
            .refundReason(reqDTO.getReason())
            .notifyUrl(reqDTO.getNotifyUrl())
            .build();

        // 2.1 执行请求
        PaxResult<PaxInvokeRefundOrderResponse> result = client.execute(request, PaxInvokeRefundOrderResponse.class);
        if (!result.checkSuccess()) { // 不成功，例如说订单不存在
            return PayRefundRespDTO.failureOf(result.getCode(), result.getMsg(), reqDTO.getOutRefundNo(), result);
        }
        // 2.2 等待回调结果
        PaxInvokeRefundOrderResponse response = result.getData();
        if (StringUtils.equals("REFUND_SUCCESS", response.getRefundStatus())) {
            return PayRefundRespDTO.successOf(response.getPaxRefundId(), LocalDateTimeUtil.of(response.getSuccessTime()),
                reqDTO.getOutRefundNo(), response);
        }
        if (Objects.equals(response.getRefundStatus(), "REFUND_FAILED")) {
            return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), result);
        }
        return PayRefundRespDTO.waitingOf(response.getPaxRefundId(), reqDTO.getOutRefundNo(), response);
    }

    @Override
    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws Throwable {
        throw new UnsupportedOperationException("Pax不实现此方法");
    }

    @Override
    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        PaxOpenSdk.PaxHeader paxHeader = PaxOpenSdk.createPaxBaseHeader(headers);
        log.info("[doParseRefundNotify][paxHeader: {}]", paxHeader);
        boolean verifySign = PaxOpenSdk.verifySign(paxHeader, body, this.config.getPublicKey());
        if (!verifySign) {
            throw new IllegalArgumentException("Pax退款回调验签失败");
        }
        // 1.1 解析 body
        PaxNoticeRefundOrderStatusResponse response = JSON.parseObject(body, PaxNoticeRefundOrderStatusResponse.class);
        // 2. 解析订单的状态
        if (StringUtils.equals("REFUND_SUCCESS", response.getRefundStatus())) {
            return PayRefundRespDTO.successOf(response.getPaxRefundId(), LocalDateTimeUtil.of(response.getSuccessTime()),
                response.getMerchantRefundId(), response);
        }
        return PayRefundRespDTO.failureOf(response.getMerchantRefundId(), response);
    }

    @Override
    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws Throwable {
        // 1.1 构建 QueryRefundOrderRequest 请求
        PaxQueryRefundOrderRequest request = new PaxQueryRefundOrderRequest();
        request.setMerchantRefundId(outRefundNo);

        // 2.1 执行请求
        PaxResult<PaxQueryRefundOrderResponse> result = client.execute(request, PaxQueryRefundOrderResponse.class);
        if (!result.checkSuccess()) {
            return PayRefundRespDTO.waitingOf(null, outRefundNo, result);
        }
        // 2.2 创建返回结果
        PaxQueryRefundOrderResponse response = result.getData();
        if (StringUtils.equals("REFUND_SUCCESS", response.getRefundStatus())) {
            return PayRefundRespDTO.successOf(response.getPaxPrepayId(), LocalDateTimeUtil.of(response.getSuccessTime()),
                outRefundNo, result);
        }
        if (Objects.equals(response.getRefundStatus(), "REFUND_FAILED")) {
            return PayRefundRespDTO.failureOf(outRefundNo, result);
        }
        return PayRefundRespDTO.waitingOf(response.getPaxPrepayId(), outRefundNo, result);
    }

    @Override
    protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws Throwable {
        throw new UnsupportedOperationException("Pax不支持转账");
    }

    @Override
    protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable {
        throw new UnsupportedOperationException("Pax不支持转账");
    }

    private Integer parseStatus(String tradeStatus) {
        // 使用switch case 判断状态
        switch (tradeStatus) {
            case "CREATING":
            case "NOT_PAY":
                return PayOrderStatusRespEnum.WAITING.getStatus();
            case "PAY_SUCCESS":
                return PayOrderStatusRespEnum.SUCCESS.getStatus();
            case "PAY_ERROR":
            case "PAY_FAILED":
            case "PAY_CLOSE":
            case "CREATION_FAILED":
                return PayOrderStatusRespEnum.CLOSED.getStatus();
            default:
                break;
        }
        log.info("支付状态不在处理范围内, status={}", tradeStatus);
        return null;
    }

    protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, PaxResult<PaxCreatePrePayOrderResponse> response) {
        Assert.isFalse(response.checkSuccess());
        return PayOrderRespDTO.closedOf(response.getCode(), response.getMsg(),
            reqDTO.getOutTradeNo(), response);
    }

    @NotNull
    protected PayOrderRespDTO buildSuccessPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, PaxResult<PaxCreatePrePayOrderResponse> result) {
        // 默认可以使用APP模式
        String displayMode = PayOrderDisplayModeEnum.APP.getMode();
        // 大额支付，需要用户输入密码，所以返回 waiting。此时，前端一般会进行轮询
        return PayOrderRespDTO.waitingOf(displayMode, result.getData().getCallPayMethodExpend(),
            reqDTO.getOutTradeNo(), result);
    }

    protected String formatAmount(Integer amount) {
        return String.valueOf(amount / 100.0);
    }

    protected BigDecimal formatAmountDecimal(Integer amount) {
        return BigDecimal.valueOf(amount).divide(BigDecimal.valueOf(100.0), 2, BigDecimal.ROUND_HALF_UP);
    }

    protected void buildCreatePrePayOrderRequest(PayOrderUnifiedReqDTO reqDTO, PaxCreatePrePayOrderRequest request) {
        // GoodsDetail
        PaxCreatePrePayOrderRequest.GoodsDetail goodsDetail = new PaxCreatePrePayOrderRequest.GoodsDetail();
        String amount = formatAmount(reqDTO.getPrice());
        // 虚拟物品
        goodsDetail.setGoodsType(GoodsTypeEnum.VIRTUAL_GOODS.getValue());
        // 游戏与充值
        goodsDetail.setGoodsCategory(GoodsCategoryEnum.GAMES_AND_RECHARGE.getValue());
        goodsDetail.setReferenceGoodsId(IdUtil.objectId());
        goodsDetail.setGoodsName(reqDTO.getSubject());
        goodsDetail.setGoodsAmount(amount);

        List<PaxCreatePrePayOrderRequest.GoodsDetail> goodsDetails = new ArrayList<>();
        goodsDetails.add(goodsDetail);

        // Creating device info
        PaxCreatePrePayOrderRequest.DeviceInfo deviceInfo = new PaxCreatePrePayOrderRequest.DeviceInfo();
        deviceInfo.setDeviceIp(reqDTO.getUserIp());

        // Creating the request
        request.setMerchantTradeNo(reqDTO.getOutTradeNo());
        request.setPayChannel(PayChannelEnum.Binance.getValue());
        request.setOrderAmount(amount);
        request.setPayMethod(PayMethodEnum.WEB_PAY.getValue());
        request.setTerminalType(TerminalTypeEnum.WEB.getValue());
        request.setGoodsDetails(goodsDetails);
        request.setGoodsDescription(reqDTO.getBody());
        request.setCurrency(CurrencyEnum.USDT.getValue());
        Duration duration = Duration.between(LocalDateTime.now(), reqDTO.getExpireTime());
        Long millis = duration.toMillis() + 1;
        request.setOrderExpireTime(millis);
        request.setDeviceInfo(deviceInfo);
        request.setNotifyUrl(reqDTO.getNotifyUrl());
    }

}
